package com.twotoasters.clusterkraf.sample;
import java.io.Serializable;
import java.util.ArrayList;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.util.DisplayMetrics;
import android.view.MenuItem;
import android.view.Window;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.CameraPosition;
import com.twotoasters.clusterkraf.Clusterkraf;
import com.twotoasters.clusterkraf.Clusterkraf.ProcessingListener;
import com.twotoasters.clusterkraf.InputPoint;
import com.twotoasters.clusterkraf.Options.ClusterClickBehavior;
import com.twotoasters.clusterkraf.Options.ClusterInfoWindowClickBehavior;
import com.twotoasters.clusterkraf.Options.SinglePointClickBehavior;
import com.twotoasters.clusterkraf.sample.RandomPointsProvider.GenerateCallback;
import com.twotoasters.clusterkraf.sample.RandomPointsProvider.GeographicDistribution;
public class SampleActivity extends FragmentActivity implements GenerateCallback, ProcessingListener {
public static final String EXTRA_OPTIONS = "options";
private static final String KEY_CAMERA_POSITION = "camera position";
private static final long DELAY_CLUSTERING_SPINNER_MILLIS = 200l;
private final Handler handler = new Handler();
private Options options;
private GoogleMap map;
private CameraPosition restoreCameraPosition;
private Clusterkraf clusterkraf;
private ArrayList<InputPoint> inputPoints;
private DelayedIndeterminateProgressBarRunnable delayedIndeterminateProgressBarRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setProgressBarIndeterminate(true);
setContentView(R.layout.activity_sample);
int titleFormatArg = 0;
Intent i = getIntent();
if (i != null) {
Object options = i.getSerializableExtra(EXTRA_OPTIONS);
if (options instanceof Options) {
this.options = (Options)options;
titleFormatArg = R.string.mode_advanced_label;
}
}
if (this.options == null) {
this.options = new Options();
titleFormatArg = R.string.mode_normal_label;
}
setTitle(getString(R.string.sample_activity, getString(titleFormatArg)));
RandomPointsProvider rpp = RandomPointsProvider.getInstance();
if (savedInstanceState != null && rpp.hasPoints()) {
this.restoreCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
this.inputPoints = rpp.getPoints();
} else {
setProgressBarIndeterminateVisibility(true);
rpp.generate(this, options.geographicDistribution, options.pointCount);
}
initMap();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onPause() {
super.onPause();
/**
* When pausing, we clear all of the clusterkraf's markers in order to
* conserve memory. When (if) we resume, we can rebuild from where we
* left off.
*/
if (clusterkraf != null) {
clusterkraf.clear();
clusterkraf = null;
if (map != null) {
restoreCameraPosition = map.getCameraPosition();
}
}
}
@Override
protected void onResume() {
super.onResume();
initMap();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (map != null) {
CameraPosition cameraPosition = map.getCameraPosition();
if (cameraPosition != null) {
outState.putParcelable(KEY_CAMERA_POSITION, cameraPosition);
}
}
}
private void initMap() {
if (map == null) {
SupportMapFragment mapFragment = (SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map);
if (mapFragment != null) {
map = mapFragment.getMap();
if (map != null) {
UiSettings uiSettings = map.getUiSettings();
uiSettings.setAllGesturesEnabled(false);
uiSettings.setScrollGesturesEnabled(true);
uiSettings.setZoomGesturesEnabled(true);
map.setOnCameraChangeListener(new OnCameraChangeListener() {
@Override
public void onCameraChange(CameraPosition arg0) {
moveMapCameraToBoundsAndInitClusterkraf();
}
});
}
}
} else {
moveMapCameraToBoundsAndInitClusterkraf();
}
}
private void moveMapCameraToBoundsAndInitClusterkraf() {
if (map != null && options != null && inputPoints != null) {
try {
if (restoreCameraPosition != null) {
/**
* if a restoreCameraPosition is available, move the camera
* there
*/
map.moveCamera(CameraUpdateFactory.newCameraPosition(restoreCameraPosition));
restoreCameraPosition = null;
} else {
/**
* otherwise, move the camera over the Two Toasters office
* at an appropriate zoom based on the user's choice of
* geographic distribution
*/
float zoom = options.geographicDistribution == GeographicDistribution.NearTwoToasters ? 11 : 4;
map.moveCamera(CameraUpdateFactory.newLatLngZoom(MarkerData.TwoToasters.getLatLng(), zoom));
}
initClusterkraf();
} catch (IllegalStateException ise) {
// no-op
}
}
}
private void initClusterkraf() {
if (map != null && inputPoints != null && inputPoints.size() > 0) {
com.twotoasters.clusterkraf.Options options = new com.twotoasters.clusterkraf.Options();
applyDemoOptionsToClusterkrafOptions(options);
clusterkraf = new Clusterkraf(map, options, inputPoints);
}
}
/**
* Applies the sample.SampleActivity.Options chosen in Normal or Advanced
* mode menus to the clusterkraf.Options which will be used to construct our
* Clusterkraf instance
*
* @param options
*/
private void applyDemoOptionsToClusterkrafOptions(com.twotoasters.clusterkraf.Options options) {
options.setTransitionDuration(this.options.transitionDuration);
/**
* this is probably not how you would set an interpolator in your own
* app. You would probably have just one that you wanted to hard code in
* your app (show me the mobile app user who actually wants to fiddle
* with the interpolator used in their animations), so you would do
* something more like `options.setInterpolator(new
* DecelerateInterpolator());` rather than mess around with reflection.
*/
Interpolator interpolator = null;
try {
interpolator = (Interpolator)Class.forName(this.options.transitionInterpolator).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
options.setTransitionInterpolator(interpolator);
/**
* Clusterkraf calculates whether InputPoint objects should join a
* cluster based on their pixel proximity. If you want to offer your app
* on devices with different screen densities, you should identify a
* Device Independent Pixel measurement and convert it to pixels based
* on the device's screen density at runtime.
*/
options.setPixelDistanceToJoinCluster(getPixelDistanceToJoinCluster());
options.setZoomToBoundsAnimationDuration(this.options.zoomToBoundsAnimationDuration);
options.setShowInfoWindowAnimationDuration(this.options.showInfoWindowAnimationDuration);
options.setExpandBoundsFactor(this.options.expandBoundsFactor);
options.setSinglePointClickBehavior(this.options.singlePointClickBehavior);
options.setClusterClickBehavior(this.options.clusterClickBehavior);
options.setClusterInfoWindowClickBehavior(this.options.clusterInfoWindowClickBehavior);
/**
* Device Independent Pixel measurement should be converted to pixels
* here too. In this case, we cheat a little by using a Drawable's
* height. It's only cheating because we don't offer a variant for that
* Drawable for every density (xxhdpi, tvdpi, others?).
*/
options.setZoomToBoundsPadding(getResources().getDrawable(R.drawable.ic_map_pin_cluster).getIntrinsicHeight());
options.setMarkerOptionsChooser(new ToastedMarkerOptionsChooser(this, inputPoints.get(0)));
options.setOnMarkerClickDownstreamListener(new ToastedOnMarkerClickDownstreamListener(this));
options.setProcessingListener(this);
}
private int getPixelDistanceToJoinCluster() {
return convertDeviceIndependentPixelsToPixels(this.options.dipDistanceToJoinCluster);
}
private int convertDeviceIndependentPixelsToPixels(int dip) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return Math.round(displayMetrics.density * dip);
}
@Override
public void onRandomMarkersGenerated(ArrayList<InputPoint> inputPoints) {
this.inputPoints = inputPoints;
initMap();
}
static class Options implements Serializable {
private static final long serialVersionUID = 7492713360265465944L;
// sample app-specific options
int pointCount = 100;
GeographicDistribution geographicDistribution = GeographicDistribution.NearTwoToasters;
// clusterkraf library options
int transitionDuration = 500;
String transitionInterpolator = LinearInterpolator.class.getCanonicalName();
int dipDistanceToJoinCluster = 100;
int zoomToBoundsAnimationDuration = 500;
int showInfoWindowAnimationDuration = 500;
double expandBoundsFactor = 0.5d;
SinglePointClickBehavior singlePointClickBehavior = SinglePointClickBehavior.SHOW_INFO_WINDOW;
ClusterClickBehavior clusterClickBehavior = ClusterClickBehavior.ZOOM_TO_BOUNDS;
ClusterInfoWindowClickBehavior clusterInfoWindowClickBehavior = ClusterInfoWindowClickBehavior.ZOOM_TO_BOUNDS;
}
@Override
public void onClusteringStarted() {
if (delayedIndeterminateProgressBarRunnable == null) {
delayedIndeterminateProgressBarRunnable = new DelayedIndeterminateProgressBarRunnable(this);
handler.postDelayed(delayedIndeterminateProgressBarRunnable, DELAY_CLUSTERING_SPINNER_MILLIS);
}
}
@Override
public void onClusteringFinished() {
if (delayedIndeterminateProgressBarRunnable != null) {
handler.removeCallbacks(delayedIndeterminateProgressBarRunnable);
delayedIndeterminateProgressBarRunnable = null;
}
setProgressBarIndeterminateVisibility(false);
}
}